﻿using MiniFox.Utility;
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Net.Http.Headers;
using System.Net.Mime;
using System.Reflection.PortableExecutable;
using System.Text;

namespace MiniFox.Web
{
    /// <summary>
    /// Web应用程序组件
    /// </summary>
    static public partial class WebUtil
    {
        /// <summary>
        /// 
        /// </summary>
        public const string MIME_TYPE_JSON = "application/json";
        /// <summary>
        /// 
        /// </summary>
        public const string HTTP = "http://";
        /// <summary>
        /// 
        /// </summary>
        public const string HTTPS = "https://";
        /// <summary>
        /// 
        /// </summary>
        public const string WS = "ws://";
        /// <summary>
        /// 
        /// </summary>
        public const string WSS = "wss://";

        #region GetFullHostByUri
        /// <summary>
        /// input "http://www.xxx.com/default.htm" will return "http://www.xxx.com"
        /// input "http://www.xxx.com:8080/default.htm" will return "http://www.xxx.com:8080"
        /// </summary>
        /// <param name="uri"></param>
        /// <returns></returns>
        public static string GetFullHostByUri(Uri uri)
        {
            if (uri == null)
                return string.Empty;
            return uri.GetProtocal() + "://" + uri.Authority;
        }
        #endregion

        #region GetFullHost
        /// <summary>
        /// input "http://www.xxx.com/default.htm" will return "http://www.xxx.com"
        /// input "https://www.xxx.com:8080/default.htm" will return "https://www.xxx.com:8080"
        /// </summary>
        /// <param name="uri"></param>
        /// <returns></returns>
        public static string GetFullHost(this Uri uri)
        {
            return GetFullHostByUri(uri);
        }
        #endregion

        #region GetAuthorityByUri
        /// <summary>
        /// input "http://www.xxx.com/default.htm" will return "www.xxx.com"
        /// input "http://sub.xxx.com:8080/default.htm" will return "sub.xxx.com:8080"
        /// </summary>
        /// <param name="uri"></param>
        /// <returns></returns>
        public static string GetAuthorityByUri(Uri uri)
        {
            if (uri == null)
                return string.Empty;
            return uri.Authority;
        }

        #endregion

        #region GetAuthority
        /// <summary>
        /// input "http://www.xxx.com/default.htm" will return "www.xxx.com",
        /// input "http://sub.xxx.com:8080/default.htm" will return "sub.xxx.com:8080"
        /// </summary>
        /// <param name="uri"></param>
        /// <returns></returns>
        public static string GetAuthority(this Uri uri)
        {
            return GetAuthorityByUri(uri);
        }
        #endregion

        #region GetHostNameByUri
        /// <summary>
        /// input "http://www.xxx.com/default.htm" will return "www.xxx.com",
        /// input "http://sub.xxx.com:8080/default.htm" will return "sub.xxx.com"
        /// </summary>
        /// <param name="uri"></param>
        /// <returns></returns>
        public static string GetHostNameByUri(Uri uri)
        {
            if (uri == null)
                return string.Empty;
            return uri.Host;
        }
        #endregion

        #region GetHostName
        /// <summary>
        /// input "http://www.xxx.com/default.htm" will return "www.xxx.com"
        /// input "http://sub.xxx.com:8080/default.htm" will return "sub.xxx.com"
        /// </summary>
        /// <param name="uri"></param>
        /// <returns></returns>
        public static string GetHostName(this Uri uri)
        {
            return GetHostNameByUri(uri);
        }
        #endregion

        #region GetProtocalByUri
        /// <summary>
        /// input "http://www.xxx.com/default.htm" will return "http",
        /// input "https://sub.xxx.com:8080/default.htm" will return "https"
        /// </summary>
        /// <param name="uri"></param>
        /// <returns></returns>
        public static string GetProtocalByUri(Uri uri)
        {
            if (uri == null)
                throw new InvalidOperationException();
            return uri.Scheme;
        }
        #endregion

        #region GetProtocal
        /// <summary>
        /// input "http://www.xxx.com/default.htm" will return "http",
        /// input "https://sub.xxx.com:8080/default.htm" will return "https"
        /// </summary>
        /// <param name="uri"></param>
        /// <returns></returns>
        public static string GetProtocal(this Uri uri)
        {
            return GetProtocalByUri(uri);
        }
        #endregion

        #region GetPortByUri
        /// <summary>
        /// input "http://www.xxx.com/default.htm" will return "80",
        /// input "https://sub.xxx.com:8080/default.htm" will return "8080"
        /// </summary>
        /// <param name="uri"></param>
        /// <returns></returns>
        public static int GetPortNumberByUri(Uri uri)
        {
            if (uri == null)
                throw new InvalidOperationException();
            return uri.Port;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="showDefaultAsNull"></param>
        /// <returns></returns>
        public static string GetPortByUri(Uri uri, bool showDefaultAsNull = false)
        {
            int defaultPort;
            switch (uri.Scheme)
            {
                case "https":
                    defaultPort = 443;
                    break;
                case "http":
                    defaultPort = 80;
                    break;
                case "ftp":
                    defaultPort = 21;
                    break;
                default:
                    defaultPort = 80;
                    break;
            }
            int port = GetPortNumberByUri(uri);
            if (port == defaultPort && showDefaultAsNull)
                return string.Empty;
            return port.ToString();
        }
        #endregion

        #region GetPort
        /// <summary>
        /// input "http://www.xxx.com/default.htm" will return "80",
        /// input "https://sub.xxx.com:8080/default.htm" will return "8080"
        /// </summary>
        /// <param name="uri"></param>
        /// <returns></returns>
        public static int GetPortNumber(this Uri uri)
        {
            return GetPortNumberByUri(uri);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="showDefaultAsNull"></param>
        /// <returns></returns>
        public static string GetPort(this Uri uri, bool showDefaultAsNull = false)
        {
            return GetPortByUri(uri, showDefaultAsNull);
        }
        #endregion

        #region GetFullAbsolutionPathByUri
        /// <summary>
        /// 获取绝对路径，但不包含querystring
        /// </summary>
        /// <param name="uri"></param>
        /// <returns></returns>
        public static string GetFullAbsolutionPathByUri(Uri uri)
        {
            return uri.GetFullHost() + uri.AbsolutePath;
        }
        #endregion

        #region GetFullAbsolutionPath
        /// <summary>
        /// 获取绝对路径，但不包含querystring
        /// </summary>
        /// <param name="uri"></param>
        /// <returns></returns>
        public static string GetFullAbsolutionPath(this Uri uri)
        {
            return GetFullAbsolutionPathByUri(uri);
        }

        #endregion

        #region GetQueryStringByUri
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uriString"></param>
        /// <returns></returns>
        public static NameValueCollection GetQueryStringByUri(string uriString)
        {
            return GetQueryStringByUri(new Uri(uriString));
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uri"></param>
        /// <returns></returns>
        public static NameValueCollection GetQueryStringByUri(Uri uri)
        {
            NameValueCollection nv = new NameValueCollection();
            string query = uri.Query;
            if (string.IsNullOrWhiteSpace(query))
                return nv;

            query = query.Trim('?');

            if (string.IsNullOrWhiteSpace(query))
                return nv;
            string[] args = query.Split('&');

            foreach (string arg in args)
            {
                if (string.IsNullOrWhiteSpace(arg))
                    continue;
                string[] keyvalue = arg.Split('=');
                if (keyvalue != null && keyvalue.Length > 0 && !string.IsNullOrWhiteSpace(keyvalue[0]))
                {
                    nv.Add(keyvalue[0], keyvalue[1]);
                }
            }
            return nv;
        }

        #endregion

        #region GetQueryString
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uriString"></param>
        /// <returns></returns>
        public static NameValueCollection GetQueryString(this string uriString)
        {
            return GetQueryStringByUri(uriString);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uri"></param>
        /// <returns></returns>
        public static NameValueCollection GetQueryString(this Uri uri)
        {
            return GetQueryStringByUri(uri);
        }
        #endregion

        #region SetQueryStringByUri
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uriString"></param>
        /// <param name="queryString"></param>
        /// <returns></returns>
        public static string AddQueryStringByUri(string uriString, string queryString)
        {
            if (string.IsNullOrWhiteSpace(uriString) || string.IsNullOrWhiteSpace(queryString))
                return uriString;

            queryString = queryString.Replace("?", string.Empty);
            queryString = queryString.Trim('&');

            string url;
            if (uriString.Contains('?'))
            {
                if (uriString.EndsWith("?"))
                    url = uriString + queryString;
                else
                    url = uriString.Trim('&') + "&" + queryString;
            }
            else
            {
                url = uriString + "?" + queryString;
            }
            return url;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uriString"></param>
        /// <param name="name"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public static string AddQueryStringByUri(string uriString, string name, string value)
        {
            if (string.IsNullOrWhiteSpace(uriString))
                return uriString;

            return AddQueryStringByUri(uriString, name + "=" + (string.IsNullOrEmpty(value) ? string.Empty : value.UrlEncode()));
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uriString"></param>
        /// <param name="param"></param>
        /// <returns></returns>
        public static string AddQueryStringByUri(string uriString, IDictionary param)
        {
            if (string.IsNullOrWhiteSpace(uriString))
                return uriString;

            if (param == null)
                return uriString;
            return AddQueryStringByUri(uriString, param.ToQueryString());
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uriString"></param>
        /// <param name="param"></param>
        /// <returns></returns>
        public static string AddQueryStringByUri(string uriString, object param)
        {
            if (string.IsNullOrWhiteSpace(uriString))
                return uriString;

            if (param == null)
                return uriString;
            return AddQueryStringByUri(uriString, param.ToQueryString());
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uriString"></param>
        /// <param name="param"></param>
        /// <returns></returns>
        public static string AddQueryStringByUri<TKey, TValue>(string uriString, IDictionary<TKey, TValue> param)
        {
            if (string.IsNullOrWhiteSpace(uriString))
                return uriString;

            if (param == null)
                return uriString;
            return AddQueryStringByUri(uriString, param.ToQueryString());
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uriString"></param>
        /// <param name="name"></param>
        /// <returns></returns>
        public static string RemoveQueryStringByUri(string uriString, params string[] name)
        {
            if (string.IsNullOrWhiteSpace(uriString) || name == null || name.Length <= 0)
                return uriString;

            if (!uriString.Contains('?'))
                return uriString;

            var arr = uriString.Split('?');
            var uri = arr[0];
            var queryString = arr[1];
            if (string.IsNullOrEmpty(queryString))
                return uriString;

            var queryArr = queryString.Split("&");

            NameValueCollection nv = new();
            foreach ( var item in queryArr)
            {
                var kv = item.Split('=');
                nv.Add(kv[0], kv[1]);
            }
            
            foreach (var n in name)
                nv.Remove(n);
            return $"{uri}?{nv.ToQueryString()}";
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uriString"></param>
        /// <returns></returns>
        public static string ClearQueryStringByUri(string uriString)
        {
            if (string.IsNullOrWhiteSpace(uriString))
                return uriString;

            if (!uriString.Contains('?'))
                return uriString;

            var arr = uriString.Split('?');
            var uri = arr[0];
            var queryString = arr[1];
            return uri;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="queryString"></param>
        /// <returns></returns>
        public static Uri AddQueryStringByUri(Uri uri, string queryString)
        {
            if (string.IsNullOrWhiteSpace(queryString))
                return uri;
            queryString = queryString.Replace("?", string.Empty);
            queryString = queryString.Trim('&');

            string baseUrl = uri.GetFullAbsolutionPath();
            string url;
            if (string.IsNullOrWhiteSpace(uri.Query))
            {
                url = baseUrl + "?" + queryString;
            }
            else
            {
                if (uri.Query.EndsWith("?"))
                    url = baseUrl + uri.Query + queryString;
                else
                    url = baseUrl + uri.Query.Trim('&') + "&" + queryString;
            }
            url += uri.Fragment;
            return new Uri(url);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="name"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public static Uri AddQueryStringByUri(Uri uri, string name, string value)
        {
            return AddQueryStringByUri(uri, name + "=" + (string.IsNullOrEmpty(value) ? string.Empty : value.UrlEncode()));
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="param"></param>
        /// <returns></returns>
        public static Uri AddQueryStringByUri(Uri uri, IDictionary param)
        {
            if (param == null)
                return uri;
            return AddQueryStringByUri(uri, param.ToQueryString());
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="param"></param>
        /// <returns></returns>
        public static Uri AddQueryStringByUri(Uri uri, object param)
        {
            if (param == null)
                return uri;
            return AddQueryStringByUri(uri, param.ToQueryString());
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="param"></param>
        /// <returns></returns>
        public static Uri AddQueryStringByUri<TKey, TValue>(Uri uri, IDictionary<TKey, TValue> param)
        {
            if (param == null)
                return uri;
            return AddQueryStringByUri(uri, param.ToQueryString());
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="name"></param>
        /// <returns></returns>
        public static Uri RemoveQueryStringByUri(Uri uri, params string[] name)
        {
            if (name == null || name.Length <= 0)
                return uri;
            NameValueCollection nv = uri.GetQueryString();
            if (nv.Count <= 0)
            {
                return uri;
            }

            foreach (var n in name)
                nv.Remove(n);
            string uriString = uri.GetFullAbsolutionPath() + "?" + nv.ToQueryString() + uri.Fragment;
            return new Uri(uriString);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uri"></param>
        /// <returns></returns>
        public static Uri ClearQueryStringByUri(Uri uri)
        {
            NameValueCollection nv = uri.GetQueryString();
            if (nv.Count <= 0)
            {
                return uri;
            }

            string uriString = uri.GetFullAbsolutionPath() + uri.Fragment;
            return new Uri(uriString);
        }

        #endregion

        #region SetQueryString
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uriString"></param>
        /// <param name="queryString"></param>
        /// <returns></returns>
        public static string AddQueryString(this string uriString, string queryString)
        {
            return AddQueryStringByUri(uriString, queryString);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="queryString"></param>
        /// <returns></returns>
        public static Uri AddQueryString(this Uri uri, string queryString)
        {
            return AddQueryStringByUri(uri, queryString);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uriString"></param>
        /// <param name="name"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public static string AddQueryString(this string uriString, string name, string value)
        {
            if (string.IsNullOrWhiteSpace(uriString))
                return uriString;

            return AddQueryStringByUri(uriString, name + "=" + (string.IsNullOrEmpty(value) ? string.Empty : value.UrlEncode()));
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="name"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public static Uri AddQueryString(this Uri uri, string name, string value)
        {
            return AddQueryStringByUri(uri, name, value);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uriString"></param>
        /// <param name="param"></param>
        /// <returns></returns>
        public static string AddQueryString(this string uriString, IDictionary param)
        {
            if (string.IsNullOrWhiteSpace(uriString))
                return uriString;

            return AddQueryStringByUri(uriString, param);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="param"></param>
        /// <returns></returns>
        public static Uri AddQueryString(this Uri uri, IDictionary param)
        {
            return AddQueryStringByUri(uri, param);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uriString"></param>
        /// <param name="param"></param>
        /// <returns></returns>
        public static string AddQueryString<TKey, TValue>(this string uriString, IDictionary<TKey, TValue> param)
        {
            if (string.IsNullOrWhiteSpace(uriString))
                return uriString;

            return AddQueryStringByUri(uriString, param);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="param"></param>
        /// <returns></returns>
        public static Uri AddQueryString<TKey, TValue>(this Uri uri, IDictionary<TKey, TValue> param)
        {
            return AddQueryStringByUri(uri, param);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uriString"></param>
        /// <param name="param"></param>
        /// <returns></returns>
        public static string AddQueryString(this string uriString, object param)
        {
            return AddQueryStringByUri(uriString, param);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="param"></param>
        /// <returns></returns>
        public static Uri AddQueryString(this Uri uri, object param)
        {
            return AddQueryStringByUri(uri, param);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uriString"></param>
        /// <param name="name"></param>
        /// <returns></returns>
        public static string RemoveQueryString(this string uriString, params string[] name)
        {
            return RemoveQueryStringByUri(uriString, name);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="name"></param>
        /// <returns></returns>
        public static Uri RemoveQueryString(this Uri uri, params string[] name)
        {
            return RemoveQueryStringByUri(uri, name);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="uriString"></param>
        /// <returns></returns>
        public static string ClearQueryString(this string uriString)
        {
            return ClearQueryStringByUri(uriString);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uri"></param>
        /// <returns></returns>
        public static Uri ClearQueryString(this Uri uri)
        {
            return ClearQueryStringByUri(uri);
        }
        #endregion

        #region GetHashByUri
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uriString"></param>
        /// <returns></returns>
        public static string GetHashByUri(string uriString)
        {
            string[] arr = uriString.Split('#');
            if (arr.Length > 1)
                return arr.Last();
            else
                return string.Empty;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uri"></param>
        /// <returns></returns>
        public static string GetHashByUri(Uri uri)
        {
            return GetHashByUri(uri.ToString());
        }

        #endregion

        #region GetHash
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uriString"></param>
        /// <returns></returns>
        public static string GetHash(this string uriString)
        {
            return GetHashByUri(uriString);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uri"></param>
        /// <returns></returns>
        public static string GetHash(this Uri uri)
        {
            return GetHashByUri(uri);
        }
        #endregion

        #region 对象转 querystring 参数
        /// <summary>
        /// 
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public static string ToQueryString(this object obj)
        {
            if (obj == null)
                return null;
            if (obj is IDictionary)
                return ((IDictionary)obj).ToQueryString();
            if (obj is NameValueCollection)
                return ((NameValueCollection)obj).ToQueryString();
            return obj.ToDictionary().ToQueryString();
        }
        /// <summary>
        /// 组装请求参数。
        /// </summary>
        /// <param name="param">Key-Value形式请求参数字典</param>
        /// <returns>URL编码后的请求数据</returns>
        public static string ToQueryString(this IDictionary param)
        {
            string[] dataArray = new string[param.Count];

            int i = 0;
            foreach (object key in param.Keys)
            {
                string name = key.ToString();
                string value = param[key] == null ? string.Empty : param[key].ToString();
                dataArray[i++] = $"{name}={value.UrlEncode()}";
            }
            string postData = string.Join("&", dataArray);
            return postData;
        }
        /// <summary>
        /// 组装请求参数。
        /// </summary>
        /// <param name="param">Key-Value形式请求参数字典</param>
        /// <returns>URL编码后的请求数据</returns>
        public static string ToQueryString<TKey, TValue>(this IDictionary<TKey, TValue> param)
        {
            string[] dataArray = new string[param.Count];

            int i = 0;
            foreach (TKey key in param.Keys)
            {
                string name = key.ToString();
                string value = param[key] == null ? string.Empty : param[key].ToString();
                dataArray[i++] = $"{name}={value.UrlEncode()}";
            }
            string postData = string.Join("&", dataArray);
            return postData;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="param"></param>
        /// <returns></returns>
        public static string ToQueryString(this NameValueCollection param)
        {
            string[] dataArray = new string[param.Count];
            for (var i = 0; i < param.Count; i++)
            {
                string name = param.Keys[i];
                string value = param[name] == null ? string.Empty : param[name].ToString();
                dataArray[i] = $"{name}={value.UrlEncode()}";
            }
            string postData = string.Join("&", dataArray);
            return postData;
        }
        #endregion

        #region 创建完整的 http 主机头
        /// <summary>
        /// 
        /// </summary>
        /// <param name="host"></param>
        /// <param name="ssl"></param>
        /// <param name="port"></param>
        /// <returns></returns>
        public static string BuildHttpHost(string host, bool ssl = false, int? port = null)
        {
            if (string.IsNullOrWhiteSpace(host))
                throw new ArgumentNullException("host");
            string protocal = ssl ? HTTPS : HTTP;
            host = host.Trim('/');
            if (host.StartsWith(HTTP))
            {
                host = host.TrimStart(HTTP.ToCharArray());
            }
            if (host.StartsWith(HTTPS))
            {
                host = host.TrimStart(HTTPS.ToCharArray());
            }
            if (port > ushort.MaxValue || port <= ushort.MinValue)
                throw new ArgumentException(Resources.CommonMessages.MSG_InvalidArgument);

            int defaultPort = ssl ? 443 : 80;
            string portNumber = (port == null || port == defaultPort) ? string.Empty : $":{port}";

            return $"{protocal}{host}{portNumber}";
        }
        #endregion

        #region 创建完整的 ws 主机头
        /// <summary>
        /// 
        /// </summary>
        /// <param name="host"></param>
        /// <param name="ssl"></param>
        /// <param name="port"></param>
        /// <returns></returns>
        public static string BuildWsHost(string host, bool ssl = false, int? port = null)
        {
            if (string.IsNullOrWhiteSpace(host))
                throw new ArgumentNullException("host");
            string protocal = ssl ? WSS : WS;
            host = host.Trim('/');
            if (host.StartsWith(WS))
            {
                host = host.TrimStart(WS.ToCharArray());
            }
            if (host.StartsWith(WSS))
            {
                host = host.TrimStart(WSS.ToCharArray());
            }
            if (port > ushort.MaxValue || port <= ushort.MinValue)
                throw new ArgumentException(Resources.CommonMessages.MSG_InvalidArgument);

            int defaultPort = ssl ? 443 : 80;
            string portNumber = (port == null || port == defaultPort) ? string.Empty : $":{port}";

            return $"{protocal}{host}{portNumber}";
        }
        #endregion

        #region 创建完整的 http URI

        #region to uri
        /// <summary>
        /// 
        /// </summary>
        /// <param name="baseUrl"></param>
        /// <returns></returns>
        public static Uri ToUri(this string baseUrl)
        {
            return new Uri(baseUrl);
        }
        #endregion 
        /// <summary>
        /// 
        /// </summary>
        /// <param name="baseUrl"></param>
        /// <param name="relativeUrl"></param>
        /// <returns></returns>
        public static string BuildFullUrl(string baseUrl, string relativeUrl)
        {
            return BuildFullUrl(baseUrl, relativeUrl, null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="baseUrl"></param>
        /// <param name="relativeUrl"></param>
        /// <param name="query"></param>
        /// <returns></returns>
        public static string BuildFullUrl(string baseUrl, string relativeUrl, object query)
        {
            return BuildFullUrl(baseUrl, relativeUrl, query, null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="baseUrl"></param>
        /// <param name="relativeUrl"></param>
        /// <param name="query"></param>
        /// <param name="hash"></param>
        /// <returns></returns>
        public static string BuildFullUrl(string baseUrl, string relativeUrl, object query, string hash)
        {
            baseUrl = baseUrl.Trim('/');
            if (!baseUrl.StartsWith(HTTP) && !baseUrl.StartsWith(HTTPS))
                baseUrl = $"{HTTP}{baseUrl}";
            if (!string.IsNullOrWhiteSpace(relativeUrl))
            {
                relativeUrl = relativeUrl.TrimStart('~').Trim('/');
                relativeUrl = $"/{relativeUrl}";
            }
            string queryString = query == null ? string.Empty : query.ToQueryString();
            if (!string.IsNullOrWhiteSpace(queryString))
            {
                queryString = queryString.Trim('?').Trim('&');
                queryString = $"?{queryString}";
            }
            hash = hash ?? string.Empty;
            if (!string.IsNullOrWhiteSpace(hash))
            {
                hash = hash.Trim('/').Trim('#');
                hash = $"#{hash}";
            }

            string fullUrl = $"{baseUrl}{relativeUrl}{queryString}{hash}";
            return fullUrl;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="ssl"></param>
        /// <param name="host"></param>
        /// <param name="port"></param>
        /// <param name="virtualPath"></param>
        /// <param name="relativeUrl"></param>
        /// <param name="queryString"></param>
        /// <param name="hash"></param>
        /// <returns></returns>
        public static string BuildFullUrl(bool ssl, string host, int? port, string virtualPath, string relativeUrl, string queryString, string hash)
        {
            string fullHost = BuildHttpHost(host, ssl, port);
            virtualPath = virtualPath == null ? string.Empty : $"/{virtualPath.TrimStart('~').Trim('/')}";
            relativeUrl = relativeUrl == null ? string.Empty : $"/{relativeUrl.TrimStart('~').Trim('/')}";

            if (!string.IsNullOrWhiteSpace(queryString))
            {
                queryString = queryString.Trim('/').Trim('&').Trim('?');
                queryString = $"?{queryString}";
            }
            if (!string.IsNullOrWhiteSpace(hash))
            {
                hash = hash.Trim('/').Trim('#');
                hash = $"#{hash}";
            }

            string fullUrl = $"{fullHost}{virtualPath}{relativeUrl}{queryString}{hash}";
            return fullUrl;
        }
        #endregion

        #region 创建相对路径
        /// <summary>
        /// 
        /// </summary>
        /// <param name="relativeUrl"></param>
        /// <param name="query"></param>
        /// <returns></returns>
        public static string BuildRelativeUrl(string relativeUrl, object query)
        {
            return BuildRelativeUrl(relativeUrl, query, null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="relativeUrl"></param>
        /// <param name="query"></param>
        /// <param name="hash"></param>
        /// <returns></returns>
        public static string BuildRelativeUrl(string relativeUrl, object query, string hash)
        {
            string queryString = query == null ? string.Empty : query.ToQueryString();
            if (!string.IsNullOrWhiteSpace(queryString))
            {
                queryString = queryString.Trim('?').Trim('&');
                queryString = $"?{queryString}";
            }
            hash = hash ?? string.Empty;
            if (!string.IsNullOrWhiteSpace(hash))
            {
                hash = hash.Trim('/').Trim('#');
                hash = $"#{hash}";
            }

            string uri = $"{relativeUrl}{queryString}{hash}";
            return uri;
        }
        #endregion
    }

    static partial class WebUtil
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="httpRequest"></param>
        /// <param name="query"></param>
        public static void AddUriParameters(this HttpRequestMessage httpRequest, object query)
        {
            if (query == null)
                return;
            var url = httpRequest.RequestUri.AddQueryString(query);
            httpRequest.RequestUri = url;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="httpRequest"></param>
        /// <param name="headers"></param>
        public static void AddHttpHeaders(this HttpRequestMessage httpRequest, object headers)
        {
            if (headers == null)
                return;
            var dict = headers.ToDictionary();
            foreach (var kv in dict)
            {
                if (kv.Value != null)
                    httpRequest.Headers.Add(kv.Key, kv.Value.ToString());
            }
        }


        #region 创建 http request 对象

        private static HttpRequestMessage CreateHttpRequestMessage(Uri uri, HttpMethod httpMethod, object headers)
        {
            httpMethod = httpMethod ?? HttpMethod.Get;
            var httpRequest = new HttpRequestMessage(httpMethod, uri);
            httpRequest.AddHttpHeaders(headers);
            return httpRequest;
        }

        #endregion

        #region 发送 web请求

        #region http

        #region sync
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="httpMethod"></param>
        /// <param name="headers"></param>
        /// <param name="requestContent"></param>
        /// <returns></returns>
        public static HttpContent Http(this Uri uri, HttpMethod httpMethod, object headers, HttpContent requestContent)
        {
            var httpRequest = CreateHttpRequestMessage(uri, httpMethod, headers);
            if (requestContent != null)
                httpRequest.Content = requestContent;
            var httpResponse = Http(httpRequest);
            return httpResponse.Content;
        }

        private static HttpResponseMessage Http(HttpRequestMessage request)
        {
            using (HttpClient httpClient = new HttpClient())
            {
                var task = httpClient.SendAsync(request);
                var response = task.Result;
                return response;
            }
        }

        #endregion

        #region async
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="httpContent"></param>
        /// <param name="httpMethod"></param>
        /// <param name="headers"></param>
        /// <returns></returns>
        public static async Task<HttpContent> HttpAsync(this Uri uri, HttpMethod httpMethod, object headers, HttpContent httpContent)
        {
            var httpRequest = CreateHttpRequestMessage(uri, httpMethod, headers);
            if (httpContent != null)
                httpRequest.Content = httpContent;
            var httpResponse = await HttpAsync(httpRequest);
            return httpResponse.Content;
        }

        private static async Task<HttpResponseMessage> HttpAsync(HttpRequestMessage request)
        {
            using (HttpClient httpClient = new HttpClient())
            {
                var response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
                return response;
            }
        }
        #endregion

        #endregion

        #region 发送GET

        #region common

        #region sync
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uri"></param>
        /// <returns></returns>
        public static HttpContent HttpGet(this Uri uri)
        {
            return HttpGet(uri, null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="query"></param>
        /// <returns></returns>
        public static HttpContent HttpGet(this Uri uri, object query)
        {
            return HttpGet(uri, query, null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="query"></param>
        /// <param name="headers"></param>
        /// <returns></returns>
        public static HttpContent HttpGet(this Uri uri, object query, object headers)
        {
            uri = uri.AddQueryString(query);
            var responseContent = Http(uri, HttpMethod.Get, headers, null);
            return responseContent;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="RS"></typeparam>
        /// <param name="uri"></param>
        /// <returns></returns>
        public static RS HttpGet<RS>(this Uri uri)
        {
            return HttpGet<RS>(uri, null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="RS"></typeparam>
        /// <param name="uri"></param>
        /// <param name="query"></param>
        /// <returns></returns>
        public static RS HttpGet<RS>(this Uri uri, object query)
        {
            return HttpGet<RS>(uri, query, null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="RS"></typeparam>
        /// <param name="uri"></param>
        /// <param name="query"></param>
        /// <param name="headers"></param>
        /// <returns></returns>
        public static RS HttpGet<RS>(this Uri uri, object query, object headers)
        {
            var responseContent = uri.HttpGet(query, headers);
            var result = responseContent.ReadAsStringAsync().Result;
            return result.ToJsonObject<RS>();
        }

        #endregion

        #region async
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uri"></param>
        /// <returns></returns>
        public static Task<HttpContent> HttpGetAsync(this Uri uri)
        {
            return HttpGetAsync(uri, null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="query"></param>
        /// <returns></returns>
        public static Task<HttpContent> HttpGetAsync(this Uri uri, object query)
        {
            return HttpGetAsync(uri, query, null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="query"></param>
        /// <param name="headers"></param>
        /// <returns></returns>
        public static Task<HttpContent> HttpGetAsync(this Uri uri, object query, object headers)
        {
            uri = uri.AddQueryString(query);
            var responseContent = HttpAsync(uri, HttpMethod.Get, headers, null);
            return responseContent;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="RS"></typeparam>
        /// <param name="uri"></param>
        /// <returns></returns>
        public static Task<RS> HttpGetAsync<RS>(this Uri uri)
        {
            return HttpGetAsync<RS>(uri, null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="RS"></typeparam>
        /// <param name="uri"></param>
        /// <param name="query"></param>
        /// <returns></returns>
        public static Task<RS> HttpGetAsync<RS>(this Uri uri, object query)
        {
            return HttpGetAsync<RS>(uri, query, null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="RS"></typeparam>
        /// <param name="uri"></param>
        /// <param name="query"></param>
        /// <param name="headers"></param>
        /// <returns></returns>
        public static async Task<RS> HttpGetAsync<RS>(this Uri uri, object query, object headers)
        {
            var response = await uri.HttpGetAsync(query, headers);
            var result = await response.ReadAsStringAsync();
            return result.ToJsonObject<RS>();
        }

        #endregion

        #endregion

        #endregion

        #region 发送POST

        #region common

        #region sync
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="requestContent"></param>
        /// <returns></returns>
        public static HttpContent HttpPost(this Uri uri, HttpContent requestContent)
        {
            return HttpPost(uri, requestContent, null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="requestContent"></param>
        /// <param name="query"></param>
        /// <returns></returns>
        public static HttpContent HttpPost(this Uri uri, HttpContent requestContent, object query)
        {
            return HttpPost(uri, requestContent, query, null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="requestContent"></param>
        /// <param name="headers"></param>
        /// <param name="query"></param>
        /// <returns></returns>
        public static HttpContent HttpPost(this Uri uri, HttpContent requestContent, object query, object headers)
        {
            uri = uri.AddQueryString(query);
            var responseContent = Http(uri, HttpMethod.Post, headers, requestContent);
            return responseContent;
        }
        #endregion

        #region async
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="requestContent"></param>
        /// <returns></returns>
        public static Task<HttpContent> HttpPostAsync(this Uri uri, HttpContent requestContent)
        {
            return HttpPostAsync(uri, requestContent, null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="requestContent"></param>
        /// <param name="query"></param>
        /// <returns></returns>
        public static Task<HttpContent> HttpPostAsync(this Uri uri, HttpContent requestContent, object query)
        {
            return HttpPostAsync(uri, requestContent, query, null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="requestContent"></param>
        /// <param name="headers"></param>
        /// <param name="query"></param>
        /// <returns></returns>
        public static Task<HttpContent> HttpPostAsync(this Uri uri, HttpContent requestContent, object query, object headers)
        {
            uri = uri.AddQueryString(query);
            var responseContent = HttpAsync(uri, HttpMethod.Post, headers, requestContent);
            return responseContent;
        }
        #endregion

        #endregion

        #region form

        #region sync
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="formData"></param>
        /// <returns></returns>
        public static HttpContent HttpPostForm(this Uri uri, IDictionary<string, object> formData)
        {
            return HttpPostForm(uri, formData, null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="formData"></param>
        /// <param name="query"></param>
        /// <returns></returns>
        public static HttpContent HttpPostForm(this Uri uri, IDictionary<string, object> formData, object query)
        {
            return HttpPostForm(uri, formData, query, string.Empty);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="formData"></param>
        /// <param name="headers"></param>
        /// <param name="query"></param>
        /// <returns></returns>
        public static HttpContent HttpPostForm(this Uri uri, IDictionary<string, object> formData, object query, object headers)
        {
            uri = uri.AddQueryString(query);
            var postData = formData.Cast<KeyValuePair<string, string>>();
            var content = new FormUrlEncodedContent(postData);
            var responseContent = Http(uri, HttpMethod.Post, headers, content);
            return responseContent;
        }
        #endregion

        #region async
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="formData"></param>
        /// <returns></returns>
        public static Task<HttpContent> HttpPostFormAsync(this Uri uri, IDictionary<string, object> formData)
        {
            return HttpPostFormAsync(uri, formData, null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="formData"></param>
        /// <param name="query"></param>
        /// <returns></returns>
        public static Task<HttpContent> HttpPostFormAsync(this Uri uri, IDictionary<string, object> formData, object query)
        {
            return HttpPostFormAsync(uri, formData, query, string.Empty);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="formData"></param>
        /// <param name="headers"></param>
        /// <param name="query"></param>
        /// <returns></returns>
        public static async Task<HttpContent> HttpPostFormAsync(this Uri uri, IDictionary<string, object> formData, object query, object headers)
        {
            uri = uri.AddQueryString(query);
            var postData = formData.Cast<KeyValuePair<string, string>>();
            var content = new FormUrlEncodedContent(postData);
            var responseContent = await HttpAsync(uri, HttpMethod.Post, headers, content);
            return responseContent;
        }
        #endregion

        #endregion

        #region json

        #region sync
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="RQ"></typeparam>
        /// <param name="uri"></param>
        /// <param name="requestObject"></param>
        /// <returns></returns>
        public static HttpContent HttpPostJson<RQ>(this Uri uri, RQ requestObject)
        {
            return HttpPostJson(uri, requestObject, null);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="RQ"></typeparam>
        /// <param name="uri"></param>
        /// <param name="requestObject"></param>
        /// <param name="query"></param>
        /// <returns></returns>
        public static HttpContent HttpPostJson<RQ>(this Uri uri, RQ requestObject, object query)
        {
            return HttpPostJson(uri, requestObject, query, null);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="RQ"></typeparam>
        /// <param name="uri"></param>
        /// <param name="requestObject"></param>
        /// <param name="headers"></param>
        /// <param name="query"></param>
        /// <returns></returns>
        public static HttpContent HttpPostJson<RQ>(this Uri uri, RQ requestObject, object query, object headers)
        {
            var jsonData = requestObject.ToJsonString();
            var httpRequestContent = new StringContent(jsonData, Encoding.UTF8, MediaTypeNames.Application.Json);
            var httpResponseContent = HttpPost(uri, httpRequestContent, query, headers);

            return httpResponseContent;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="RQ"></typeparam>
        /// <typeparam name="RS"></typeparam>
        /// <param name="uri"></param>
        /// <param name="requestObject"></param>
        /// <returns></returns>
        public static RS HttpPostJson<RQ, RS>(this Uri uri, RQ requestObject)
        {
            return uri.HttpPostJson<RQ, RS>(requestObject, null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="RQ"></typeparam>
        /// <typeparam name="RS"></typeparam>
        /// <param name="uri"></param>
        /// <param name="requestObject"></param>
        /// <param name="query"></param>
        /// <returns></returns>
        public static RS HttpPostJson<RQ, RS>(this Uri uri, RQ requestObject, object query)
        {
            return uri.HttpPostJson<RQ, RS>(requestObject, query, null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="RQ"></typeparam>
        /// <typeparam name="RS"></typeparam>
        /// <param name="uri"></param>
        /// <param name="requestObject"></param>
        /// <param name="query"></param>
        /// <param name="headers"></param>
        /// <returns></returns>
        public static RS HttpPostJson<RQ, RS>(this Uri uri, RQ requestObject, object query, object headers)
        {
            var responseContent = uri.HttpPostJson(requestObject, query, headers);
            var result = responseContent.ReadAsStringAsync().Result;
            return result.ToJsonObject<RS>();
        }
        #endregion

        #region async
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="RQ"></typeparam>
        /// <param name="uri"></param>
        /// <param name="requestObject"></param>
        /// <returns></returns>
        public static Task<HttpContent> HttpPostJsonAsync<RQ>(this Uri uri, RQ requestObject)
        {
            return HttpPostJsonAsync(uri, requestObject, null);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="RQ"></typeparam>
        /// <param name="uri"></param>
        /// <param name="requestObject"></param>
        /// <param name="query"></param>
        /// <returns></returns>
        public static Task<HttpContent> HttpPostJsonAsync<RQ>(this Uri uri, RQ requestObject, object query)
        {
            return HttpPostJsonAsync(uri, requestObject, query, null);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="RQ"></typeparam>
        /// <param name="uri"></param>
        /// <param name="requestObject"></param>
        /// <param name="headers"></param>
        /// <param name="query"></param>
        /// <returns></returns>
        public static async Task<HttpContent> HttpPostJsonAsync<RQ>(this Uri uri, RQ requestObject, object query, object headers)
        {
            var jsonData = requestObject.ToJsonString();
            var httpRequestContent = new StringContent(jsonData, Encoding.UTF8, MediaTypeNames.Application.Json);
            var httpResponseContent = await HttpPostAsync(uri, httpRequestContent, query, headers);

            return httpResponseContent;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="RQ"></typeparam>
        /// <typeparam name="RS"></typeparam>
        /// <param name="uri"></param>
        /// <param name="requestObject"></param>
        /// <returns></returns>
        public static Task<RS> HttpPostJsonAsync<RQ, RS>(this Uri uri, RQ requestObject)
        {
            return uri.HttpPostJsonAsync<RQ, RS>(requestObject, null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="RQ"></typeparam>
        /// <typeparam name="RS"></typeparam>
        /// <param name="uri"></param>
        /// <param name="requestObject"></param>
        /// <param name="query"></param>
        /// <returns></returns>
        public static Task<RS> HttpPostJsonAsync<RQ, RS>(this Uri uri, RQ requestObject, object query)
        {
            return uri.HttpPostJsonAsync<RQ, RS>(requestObject, query, null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="RQ"></typeparam>
        /// <typeparam name="RS"></typeparam>
        /// <param name="uri"></param>
        /// <param name="requestObject"></param>
        /// <param name="query"></param>
        /// <param name="headers"></param>
        /// <returns></returns>
        public static async Task<RS> HttpPostJsonAsync<RQ, RS>(this Uri uri, RQ requestObject, object query, object headers)
        {
            var responseContent = await uri.HttpPostJsonAsync(requestObject, query, headers);
            var result = await responseContent.ReadAsStringAsync();
            return result.ToJsonObject<RS>();
        }
        #endregion

        #endregion

        #endregion

        #region multipart

        #endregion

        #region 上传文件

        /// <summary>
        /// 创建分段头部
        /// </summary>
        /// <param name="boundary"></param>
        /// <param name="formName"></param>
        /// <param name="fileName"></param>
        /// <param name="mimeType"></param>
        /// <param name="encoding"></param>
        /// <returns></returns>
        static byte[] CreatePartHeader(string boundary, string formName, string fileName, string mimeType, Encoding encoding)
        {
            mimeType = string.IsNullOrEmpty(mimeType) ? MediaTypeNames.Application.Octet : mimeType;
            string begin = $"\r\n--{boundary}\r\n";
            const string end = "\r\n\r\n";
            return encoding.GetBytes($"{begin}Content-Disposition: form-data; name=\"{formName}\"; filename=\"{fileName}\"\r\nContent-Type: {mimeType}{end}");
        }

        #region 异步读
        static async Task<MemoryStream> CreatePartMemoryStreamAsync(string boundary, IEnumerable<(string formName, string fileName, string mimeType, Stream stream)> fileDatas, Encoding encoding)
        {
            MemoryStream memoryStream = new MemoryStream();

            foreach (var file in fileDatas)
            {
                var header = CreatePartHeader(boundary, file.formName, file.fileName, file.mimeType, encoding);
                await memoryStream.WriteAsync(header, 0, header.Length);
                if (file.stream.CanSeek)
                {
                    var position = file.stream.Position;
                    file.stream.Position = 0;
                    await file.stream.CopyToAsync(memoryStream);
                    file.stream.Position = position;
                }
                else
                {
                    await file.stream.CopyToAsync(memoryStream);
                }
            }
            var end = encoding.GetBytes($"\r\n--{boundary}--");
            await memoryStream.WriteAsync(end, 0, end.Length);
            return memoryStream;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="boundary"></param>
        /// <param name="fileDatas"></param>
        /// <param name="encoding"></param>
        /// <returns></returns>
        public static async Task<byte[]> CreatePartContentsAsync(string boundary, IEnumerable<(string formName, string fileName, string mimeType, Stream stream)> fileDatas, Encoding encoding)
        {
            using (MemoryStream memoryStream = await CreatePartMemoryStreamAsync(boundary, fileDatas, encoding))
            {
                return memoryStream.ToArray();
            }
        }
        static async Task<MemoryStream> CreatePartMemoryStreamAsync(string boundary, IEnumerable<(string formName, string fileName, string mimeType, byte[] data)> fileDatas, Encoding encoding)
        {
            MemoryStream memoryStream = new MemoryStream();

            foreach (var file in fileDatas)
            {
                var header = CreatePartHeader(boundary, file.formName, file.fileName, file.mimeType, encoding);
                await memoryStream.WriteAsync(header, 0, header.Length);
                await memoryStream.WriteAsync(file.data, 0, file.data.Length);
            }
            var end = encoding.GetBytes($"\r\n--{boundary}--");
            await memoryStream.WriteAsync(end, 0, end.Length);
            return memoryStream;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="boundary"></param>
        /// <param name="fileDatas"></param>
        /// <param name="encoding"></param>
        /// <returns></returns>
        public static async Task<byte[]> CreatePartContentsAsync(string boundary, IEnumerable<(string formName, string fileName, string mimeType, byte[] data)> fileDatas, Encoding encoding)
        {
            using (MemoryStream memoryStream = await CreatePartMemoryStreamAsync(boundary, fileDatas, encoding))
            {
                return memoryStream.ToArray();
            }
        }
        #endregion

        #region 同步读

        static MemoryStream CreatePartMemoryStream(string boundary, IEnumerable<(string formName, string fileName, string mimeType, Stream stream)> fileDatas, Encoding encoding)
        {
            MemoryStream memoryStream = new MemoryStream();
            foreach (var file in fileDatas)
            {
                var header = CreatePartHeader(boundary, file.formName, file.fileName, file.mimeType, encoding);
                memoryStream.Write(header, 0, header.Length);
                if (file.stream.CanSeek)
                {
                    var position = file.stream.Position;
                    file.stream.Position = 0;
                    file.stream.CopyTo(memoryStream);
                    file.stream.Position = position;
                }
                else
                {
                    file.stream.CopyTo(memoryStream);
                }
            }
            var end = encoding.GetBytes($"\r\n--{boundary}--");
            memoryStream.Write(end, 0, end.Length);
            return memoryStream;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="boundary"></param>
        /// <param name="fileDatas"></param>
        /// <param name="encoding"></param>
        /// <returns></returns>
        public static byte[] CreatePartContents(string boundary, IEnumerable<(string formName, string fileName, string mimeType, Stream stream)> fileDatas, Encoding encoding)
        {
            using (MemoryStream memoryStream = CreatePartMemoryStream(boundary, fileDatas, encoding))
            {
                return memoryStream.ToArray();
            }
        }

        static MemoryStream CreatePartMemoryStream(string boundary, IEnumerable<(string formName, string fileName, string mimeType, byte[] data)> fileDatas, Encoding encoding)
        {
            MemoryStream memoryStream = new MemoryStream();

            foreach (var file in fileDatas)
            {
                var header = CreatePartHeader(boundary, file.formName, file.fileName, file.mimeType, encoding);
                memoryStream.Write(header, 0, header.Length);
                memoryStream.Write(file.data, 0, file.data.Length);
            }
            var end = encoding.GetBytes($"\r\n--{boundary}--");
            memoryStream.Write(end, 0, end.Length);
            return memoryStream;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="boundary"></param>
        /// <param name="fileDatas"></param>
        /// <param name="encoding"></param>
        /// <returns></returns>
        public static byte[] CreatePartContents(string boundary, IEnumerable<(string formName, string fileName, string mimeType, byte[] data)> fileDatas, Encoding encoding)
        {
            using (MemoryStream memoryStream = CreatePartMemoryStream(boundary, fileDatas, encoding))
            {
                return memoryStream.ToArray();
            }
        }
        #endregion

        #region sync

        /// <summary>
        /// 
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="fileDatas"></param>
        /// <returns></returns>
        public static HttpContent HttpPostFiles(Uri uri, IEnumerable<(string formName, string fileName, string mimeType, byte[] data)> fileDatas)
        {
            return HttpPostFiles(uri, fileDatas, null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="fileDatas"></param>
        /// <param name="headers"></param>
        /// <returns></returns>
        public static HttpContent HttpPostFiles(Uri uri, IEnumerable<(string formName, string fileName, string mimeType, byte[] data)> fileDatas, object headers)
        {
            return HttpPostFiles(uri, fileDatas, headers, null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="fileDatas"></param>
        /// <param name="headers"></param>
        /// <param name="query"></param>
        /// <returns></returns>
        public static HttpContent HttpPostFiles(Uri uri, IEnumerable<(string formName, string fileName, string mimeType, byte[] data)> fileDatas, object headers, object query)
        {
            return HttpPostFiles(uri, fileDatas, headers, query, Encoding.UTF8);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="fileDatas"></param>
        /// <param name="headers"></param>
        /// <param name="query"></param>
        /// <param name="encoding"></param>
        /// <returns></returns>
        public static HttpContent HttpPostFiles(Uri uri, IEnumerable<(string formName, string fileName, string mimeType, byte[] data)> fileDatas, object headers, object query, Encoding encoding)
        {
            var contentType = new ContentType("multipart/form-data");
            contentType.Boundary = $"----{SequenceGUID.NewGUID()}";
            uri = uri.AddQueryString(query);
            var postData = CreatePartContents(contentType.Boundary, fileDatas, encoding);
            HttpContent httpRequestContent = new ByteArrayContent(postData);
            var httpResponseContent = Http(uri, HttpMethod.Post, headers, httpRequestContent);

            return httpResponseContent;
        }
        #endregion

        #region async

        /// <summary>
        /// 
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="fileDatas"></param>
        /// <returns></returns>
        public static Task<HttpContent> HttpPostFilesAsync(Uri uri, IEnumerable<(string formName, string fileName, string mimeType, byte[] data)> fileDatas)
        {
            return HttpPostFilesAsync(uri, fileDatas, null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="fileDatas"></param>
        /// <param name="headers"></param>
        /// <returns></returns>
        public static Task<HttpContent> HttpPostFilesAsync(Uri uri, IEnumerable<(string formName, string fileName, string mimeType, byte[] data)> fileDatas, object headers)
        {
            return HttpPostFilesAsync(uri, fileDatas, headers, null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="fileDatas"></param>
        /// <param name="headers"></param>
        /// <param name="query"></param>
        /// <returns></returns>
        public static Task<HttpContent> HttpPostFilesAsync(Uri uri, IEnumerable<(string formName, string fileName, string mimeType, byte[] data)> fileDatas, object headers, object query)
        {
            return HttpPostFilesAsync(uri, fileDatas, headers, query, Encoding.UTF8);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="fileDatas"></param>
        /// <param name="headers"></param>
        /// <param name="query"></param>
        /// <param name="encoding"></param>
        /// <returns></returns>
        public static async Task<HttpContent> HttpPostFilesAsync(Uri uri, IEnumerable<(string formName, string fileName, string mimeType, byte[] data)> fileDatas, object headers, object query, Encoding encoding)
        {
            var contentType = new ContentType("multipart/form-data");
            contentType.Boundary = $"----{SequenceGUID.NewGUID()}";
            uri = uri.AddQueryString(query);
            var postData = await CreatePartContentsAsync(contentType.Boundary, fileDatas, encoding);
            HttpContent httpRequestContent = new ByteArrayContent(postData);
            var httpResponseContent = await HttpAsync(uri, HttpMethod.Post, headers, httpRequestContent);

            return httpResponseContent;
        }
        #endregion

        #endregion


        #endregion

    }
}
